import logging
import os
import pathlib
import pprint
import re
import shutil
import sys
import tarfile
import time
from zipfile import ZipFile

import distro
import pytest

import tests.support.helpers


log = logging.getLogger(__name__)


@pytest.fixture(scope="session")
def version():
    """
    get version number from artifact
    """
    _version = ""
    for artifact in tests.support.helpers.ARTIFACTS_DIR.glob("**/*.*"):
        _version = re.search(
            r"([0-9].*)(\-[0-9].el|\+ds|\-[0-9].am|\-[0-9]-[a-z]*-[a-z]*[0-9]*.(tar.gz|zip))",
            artifact.name,
        )
        if _version:
            _version = _version.groups()[0].replace("_", "-").replace("~", "")
            break
    return _version


def install_package(pkg_mngr, pkgs):
    # install packages
    pkg_install = [pkg_mngr, "install", "-y"]
    log.debug("Installing packages:\n%s", pprint.pformat(pkgs))
    ret = tests.support.helpers.run(pkg_install + pkgs)
    assert ret["retcode"] == 0


def uninstall_package(pkg_mngr, pkgs, rm_pkg, salt_pkgs):
    # remove packages
    pkg_remove = [pkg_mngr, rm_pkg, "-y"]
    log.debug("Un-Installing packages:\n%s", pprint.pformat(pkgs))
    ret = tests.support.helpers.run(pkg_remove + salt_pkgs)
    assert ret["retcode"] == 0


def install_compressed(pkgs, salt_fix=None):
    pkg = pkgs[0]
    if sys.platform.startswith("win"):
        # Extract the files
        log.debug("Extracting zip file")
        with ZipFile(pkg, "r") as zip:
            zip.extractall(path=salt_fix.root)
        # Register the services
        # run_root and ssm_bin are configured in helper.py to point to the
        # correct binary location
        log.debug("Installing master service")
        svc_install = [
            salt_fix.ssm_bin,
            "install",
            "salt-master",
            salt_fix.run_root,
            "master",
            "-c",
            "C:\\salt\\conf",
        ]
        ret = tests.support.helpers.run(svc_install)
        log.debug("Installing minion service")
        svc_install = [
            salt_fix.ssm_bin,
            "install",
            "salt-minion",
            salt_fix.run_root,
            "minion",
            "-c",
            "C:\\salt\\conf",
        ]
        ret = tests.support.helpers.run(svc_install)
        log.debug("Installing api service")
        svc_install = [
            salt_fix.ssm_bin,
            "install",
            "salt-api",
            salt_fix.run_root,
            "api",
            "-c",
            "C:\\salt\\conf",
        ]
        ret = tests.support.helpers.run(svc_install)

    else:
        log.debug("Extracting tarball")
        with tarfile.open(pkg) as tar:  # , "r:gz")
            tar.extractall(path=salt_fix.root)


def uninstall_compressed(salt_fix):
    if sys.platform.startswith("win"):
        # Uninstall the services
        log.debug("Uninstalling master service")
        svc_remove = ["sc", "delete", "salt-master"]
        ret = tests.support.helpers.run(svc_remove)
        log.debug("Uninstalling minion service")
        svc_remove = ["sc", "delete", "salt-minion"]
        ret = tests.support.helpers.run(svc_remove)
        log.debug("Uninstalling api service")
        svc_remove = ["sc", "delete", "salt-api"]
        ret = tests.support.helpers.run(svc_remove)
        log.debug("Removing the Salt Service Manager")
        pathlib.Path.unlink(salt_fix.ssm_bin)
    if salt_fix.singlebin:
        log.debug(f"Deleting the salt binary: {str(salt_fix.run_root)}")
        pathlib.Path.unlink(salt_fix.run_root)
    else:
        log.debug(f'Deleting the onedir directory: {str(salt_fix.root / "salt")}')
        shutil.rmtree(salt_fix.root / "salt")


@pytest.fixture(autouse=True, scope="package")
def install_salt():
    """
    Install Salt
    """
    # todo make the pkg path configurable
    distro_id = distro.id().lower()
    salt_pkgs = [
        "salt-api",
        "salt-syndic",
        "salt-ssh",
        "salt-master",
        "salt-cloud",
        "salt-minion",
    ]
    salt_fix = tests.support.helpers.Pkg()
    pkgs = salt_fix.pkgs
    compressed = salt_fix.compressed

    pkg_mngr = None
    rm_pkg = None
    if distro_id in ["centos", "redhat", "amzn"]:
        salt_pkgs.append("salt")
        pkg_mngr = "yum"
        rm_pkg = "remove"

    elif distro_id in ["ubuntu", "debian"]:
        salt_pkgs.append("salt-common")
        pkg_mngr = "apt-get"
        rm_pkg = "purge"
        tests.support.helpers.run(["apt-get", "update"])

    if compressed:
        install_compressed(pkgs=pkgs, salt_fix=salt_fix)
        yield
        uninstall_compressed(salt_fix=salt_fix)
    else:
        install_package(pkg_mngr, pkgs=pkgs)
        yield
        uninstall_package(pkg_mngr, pkgs=pkgs, rm_pkg=rm_pkg, salt_pkgs=salt_pkgs)


@pytest.fixture(scope="module")
def sls():
    """
    Add an sls file
    """
    if sys.platform.startswith("win"):
        file_root = pathlib.Path("C:/salt/srv/salt")
    else:
        file_root = pathlib.Path("/srv/salt")
    file_root.mkdir(mode=0o777, parents=True, exist_ok=True)
    test_sls = file_root / "test.sls"
    state_sls = file_root / "states.sls"
    win_state_sls = file_root / "win_states.sls"
    with open(test_sls, "w") as _fh:
        _fh.write(
            """
            test_foo:
              test.succeed_with_changes:
                  - name: foo
        """
        )
    with open(state_sls, "w") as _fh:
        _fh.write(
            """
            update:
              pkg.installed:
                - name: bash
            salt_dude:
              user.present:
                - name: dude
                - fullname: Salt Dude
        """
        )
    with open(win_state_sls, "w") as _fh:
        _fh.write(
            """
            create_empty_file:
              file.managed:
                - name: C://salt/test/txt
            salt_dude:
              user.present:
                - name: dude
                - fullname: Salt Dude
        """
        )
    try:
        yield test_sls, state_sls, win_state_sls
    finally:
        shutil.rmtree(file_root.parent)


# todo: allow someone to customize config before startup of master
@pytest.fixture(scope="module")
def master_config(contents=None):
    """
    Add the Salt Master config
    """
    if sys.platform.startswith("win"):
        file_root = pathlib.Path("C:/salt/srv/salt")
    else:
        file_root = pathlib.Path("/srv/salt")
    contents = {
        "file_roots": {
            "base": [
                str(file_root),
                str(tests.support.helpers.TESTS_DIR / "install" / "files"),
            ]
        }
    }
    tests.support.helpers.write_config("master", contents=contents)

    def _config(contents):
        tests.support.helpers.write_config("master", contents=contents)

    return _config


# todo: allow someone to customize config before startup of minion
@pytest.fixture(scope="module")
def minion_config():
    """
    Add the Salt Minion config
    """
    contents = {"master": "127.0.0.1", "id": "pkg_tests"}
    tests.support.helpers.write_config("minion", contents=contents)

    def _config(id="pkg_tests", master="127.0.0.1", contents=None):
        """
        adding custom content to config
        """
        if not contents:
            contents = {}
        default = {"master": master, "id": id}
        contents.update(default)
        tests.support.helpers.write_config("minion", contents=contents)

    return _config


@pytest.fixture(scope="module")
def api_config():
    """
    Add the Salt Api config
    """
    contents = {
        "rest_cherrypy": {"port": 8000, "disable_ssl": True},
        "external_auth": {"auto": {"saltdev": [".*"]}},
    }
    tests.support.helpers.write_config(
        pathlib.Path("master.d/api.conf"), contents=contents
    )


@pytest.fixture(scope="module")
def start_master(master_config, salt_fixt):
    """
    Start up a master
    """
    try:
        tests.support.helpers.start_service(
            "salt-master",
            binary=salt_fixt.salt_bin["master"],
            compressed=salt_fixt.compressed,
        )
        yield
    finally:
        tests.support.helpers.stop_service(
            "salt-master", compressed=salt_fixt.compressed
        )


@pytest.fixture(scope="module")
def start_minion(minion_config, salt_fixt):
    """
    Start up a minion
    """
    try:
        tests.support.helpers.start_service(
            "salt-minion",
            binary=salt_fixt.salt_bin["minion"],
            compressed=salt_fixt.compressed,
        )
        yield
    finally:
        tests.support.helpers.stop_service(
            "salt-minion", compressed=salt_fixt.compressed
        )


@pytest.fixture(scope="module")
def master_minion(start_master, start_minion, salt_fixt):
    """
    Start both a master and a minion and accept keys
    """
    # accept the key after services started
    tries = 1
    try:
        while tries <= 12:
            log.debug("***** Accepting minion key")
            accept_key = tests.support.helpers.run(
                salt_fixt.salt_bin["key"] + ["-a", "pkg_tests", "-y"]
            )
            if (
                "does not match" not in accept_key["stdout"]
                and accept_key["retcode"] == 0
            ):
                break
            log.debug(f"***** Sleeping after {tries} tries")
            tries += 1
            time.sleep(5)
        log.debug(f"***** Key accepted after {tries} tries")
        time.sleep(15)
        yield
    finally:
        delete_key = tests.support.helpers.run(
            salt_fixt.salt_bin["key"] + ["-d", "pkg_tests", "-y"]
        )
        assert delete_key["retcode"] == 0


@pytest.fixture(scope="module")
def test_account():
    api_usr = tests.support.helpers.TestUser()
    try:
        api_usr.add_user()
        # setup salt-api user
        yield api_usr
    finally:
        api_usr.remove_user()


@pytest.fixture(scope="module")
def salt_api(api_config, master_minion, test_account, salt_fixt):
    """
    start up and configure salt_api
    """
    try:
        tests.support.helpers.start_service(
            "salt-api",
            binary=salt_fixt.salt_bin["api"],
            compressed=salt_fixt.compressed,
        )

        yield test_account
    finally:
        tests.support.helpers.stop_service("salt-api", compressed=salt_fixt.compressed)


@pytest.fixture(scope="module")
def salt_fixt():
    return tests.support.helpers.Pkg()


@pytest.fixture(scope="module")
def salt_pillar():
    """
    Add pillar files
    """
    if sys.platform.startswith("win"):
        pillar_root = pathlib.Path("C:/salt/srv/pillar")
    else:
        pillar_root = pathlib.Path("/srv/pillar")
    pillar_root.mkdir(mode=0o777, parents=True, exist_ok=True)
    top_sls = pillar_root / "top.sls"
    test_sls = pillar_root / "test.sls"

    with open(top_sls, "w") as _fh:
        _fh.write(
            """
            base:
              '*':
                - test
            """
        )

    with open(test_sls, "w") as _fh:
        _fh.write(
            """
            info: test
            """
        )
    try:
        yield top_sls, test_sls
    finally:
        shutil.rmtree(pillar_root.parent)
